﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using X3DXNA.services;
using Microsoft.Xna.Framework;
using X3DXNA.interfaces;
using X3DXNA.core.Boundings;
using X3DXNA.core.CustomNodes;

namespace X3DXNA.basicModels
{
    class basicIndexedFaceSet : IGeometry
    {
        VertexPositionNormal[] vertices;
        Int16[] indices;
        Vector3[] indexedVertices;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        myBasicEffect basicEffect;
        MyBoundingBox boundingBox;

        Vector3 pos, size;
        Matrix world = Matrix.Identity, rotation;
        Game game;
        public String DEF { get; set; }
        public Matrix World { get { return world; } set { world = value; } }
        public Matrix getBasicTransformation { get { return Matrix.Identity; } }

        public void applyWorldTransformation(Matrix transformation)
        {
            world *= transformation;
        }

        public void applyRotation(Matrix value)
        {
            rotation = value;
            recalculateWorld();
        }

        private void recalculateWorld()
        {
            world = Matrix.Identity * rotation * Matrix.CreateTranslation(pos) * Matrix.CreateScale(size);
        }

        public basicIndexedFaceSet(Game game, String DEF, Int16[] indices, Vector3[] vertices, Vector3[] normals, Matrix transformation)
        {
            this.DEF = DEF;
            this.game = game;
            this.indices = indices;
            this.vertices = new VertexPositionNormal[vertices.Length];

            //Conserviamo tutti i vertici indicizzati per il "Per-Triangle Control" dei TouchSensor
            storeIndexedVertices(vertices, indices);

            bool shouldICreateNormals;

            if (normals == null)
                shouldICreateNormals = true;
            else 
                shouldICreateNormals = false;

            Vector3.Transform(vertices, ref transformation, vertices);
            if (!shouldICreateNormals)
            {
                Matrix normalTransformation = Matrix.Transpose(Matrix.Invert(transformation));
                Vector3.Transform(normals, ref normalTransformation, normals);
            }

            for (int i = 0; i < vertices.Length; i++)
                if (shouldICreateNormals)
                    this.vertices[i] = new VertexPositionNormal(vertices[i], Vector3.One);
                else
                    this.vertices[i] = new VertexPositionNormal(vertices[i], normals[i]); 

            this.basicEffect = (myBasicEffect)game.Services.GetService(typeof(myBasicEffect));

            //Calcoliamo la BoundingBox dell'oggetto
            boundingBox = new MyBoundingBox(BoundingBox.CreateFromPoints(vertices));

            initializeVertexBuffer(shouldICreateNormals);
        }

        public void initializeVertexBuffer(bool shouldICreateNormals)
        {
            vertexBuffer = new VertexBuffer(
                game.GraphicsDevice, 
                typeof(VertexPositionNormal), 
                vertices.Length, 
                BufferUsage.None);

            indexBuffer = new IndexBuffer(game.GraphicsDevice, typeof(Int16), indices.Length, BufferUsage.None);

            vertexBuffer.SetData<VertexPositionNormal>(vertices, 0, vertices.Length);
            indexBuffer.SetData<Int16>(indices);

            if (shouldICreateNormals)
                createNormals(vertexBuffer, indexBuffer);
        }

        private void createNormals(VertexBuffer vertexBuffer, IndexBuffer indexBuffer)
        {
            VertexPositionNormal[] vertices = new VertexPositionNormal[vertexBuffer.VertexCount];
            vertexBuffer.GetData(vertices);
            Int16[] indices = new Int16[indexBuffer.IndexCount];
            indexBuffer.GetData(indices);

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            for (int i = 0; i < indices.Length / 3; i++)
            {
                Vector3 firstvec = vertices[indices[i * 3 + 1]].Position - vertices[indices[i * 3]].Position;
                Vector3 secondvec = vertices[indices[i * 3]].Position - vertices[indices[i * 3 + 2]].Position;
                Vector3 normal = Vector3.Cross(firstvec, secondvec);
                normal.Normalize();
                vertices[indices[i * 3]].Normal += normal;
                vertices[indices[i * 3 + 1]].Normal += normal;
                vertices[indices[i * 3 + 2]].Normal += normal;
            }

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();

            vertexBuffer.SetData(vertices);
        }

        public void draw()
        {
            game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            game.GraphicsDevice.Indices = indexBuffer;

            foreach (EffectPass pass in basicEffect.getBasicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                game.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    0,
                    0,
                    vertices.Length,
                    0,
                    indices.Length / 3
                );
            }
        }

        public IBounding retrieveBounding()
        {
            return boundingBox;
        }

        /// <summary>
        /// Ritorna l'array di vertici indicizzati per triangoli.
        /// </summary>
        /// <returns></returns>
        public Vector3[] getTriangleVertices()
        {
            return indexedVertices;
        }

        /// <summary>
        /// Funzione che "indicizza" i vertici. In base all'attuale elenco di indici, costruisce un array in cui 
        /// i vertici sono elencati 3 a 3.
        /// </summary>
        private void storeIndexedVertices(Vector3[] vertices, Int16[] indices)
        {
            List<Vector3> tempVertices = new List<Vector3>();

            foreach (Int16 index in indices)
            {
                // Look up the position of this vertex.
                Vector3 vertex = vertices[index];

                // Store this vertex.
                tempVertices.Add(vertex);
            }

            this.indexedVertices = tempVertices.ToArray();
        }
    }
}
